package pnca.analytics;


import pnca.*;
import pnca.Constants;
import MASS.*;                  // MASS Library
import java.util.*;             // Vectors, etc.
import java.util.AbstractMap.*; // for simple key-value pairs
import java.util.logging.Level;

/**
 * @name MoistureFlux
 *
 * @author Niko Simonson (Primary)
 * @author Brett Yasutake (Contributing)
 */
public class MoistureFlux extends Place {

    // Constants
    // MASS arguments
    private static final String NAME = Constants.ANALYTICSPACKAGE + "MoistureFlux";
    private static final String[] MASSARGS = Constants.MASSARGS;  // connection arguments
    private static final int NPROCESSES = Constants.NPROCESSES;    // number of processes to employ
    private static final int NTHREADS = Constants.NTHREADS;      // number of threads to employ
    private static final int MAXITERATIONS = Constants.NONTIMEBASED;
    
    // Functions
    private static final int init_ = 0;           // initialize Places
    private static final int computeMF_ = 1;      // compute regional avg temps
    private static final int storeMFResults_ = 2; // exchange regional temps
    // Northwest regional climate data sample
    private static final int XRANGE = Constants.XRANGE;              // data width (# of grid cells)
    private static final int YRANGE = Constants.YRANGE;              // data height (# of grid cells)
    // Target variables
    private static final String XWIND = Constants.XWIND;      // east-west windspeed at 10m above surface
    private static final String YWIND = Constants.YWIND;      // north-south windspeed at 10m above surface
    private static final String MOISTURE = Constants.MOISTURE;    // moisture at 2m above surface
    private static final int METADATALENGTH = Constants.METADATALENGTH;        // # of metadata elements
    
    // Variable
    private static int fileRange;               // number of computing nodes
    private static ProvenanceAdapter pa;        // Stores provenance
    private static String[] fileNames;          // names of selected source files
    private int myIndex;        // computing node's index
    
    private double[][] mf;   // moisture flux scalar
    private double[][] dir;  // moisture flux direction in radians
    private double[][] qu;   // west-east wind component * moisture
    private double[][] qv;   // south-north wind component * moisture
    private double[][] xSpd;
    private double[][] ySpd;
    private double[][] moisture;
    private ResultsAdapter ra;               // stores results
    private static Places pacNorthwest = null;   // parallel encironment
    private String resultsName;              // results files' name
    private static SimpleEntry<String, Integer[]> entryMagnitude;
    private static SimpleEntry<String, Integer[]> entryDirection;
    private static SimpleEntry<String, Integer[]> entryQU;
    private static SimpleEntry<String, Integer[]> entryQV;


    /**
     * @name MoistureFlux - Constructor initializes moisture flux and wind
     *                          direction data structures
     * @param interval unused Empty constructor required for MASS Place
     *                 implementation
     */
    public MoistureFlux(Object interval) {
        // A constructor implementation is required for MASS Place
        mf = new double[XRANGE][YRANGE];
        dir = new double[XRANGE][YRANGE];
        qu = new double[XRANGE][YRANGE];
        qv = new double[XRANGE][YRANGE];
    }

    /**
     * @name callMethod Provides enumerated list of functions to be parallelized
     * in MASS
     *
     * @param funcId - function ID (listed as Constants)
     * @param args - function parameters
     * @return - unused
     */
    @Override
    public Object callMethod(int funcId, Object args) {
        switch (funcId) {
            case init_:
                return init(args);
            case computeMF_:
                return computeMF(args);
            case storeMFResults_:
                return storeMFResults(args);
        }
        return null;
    }

    /**
     * @name init Initialization information for MASS Places index[0]: days
     * index[1]: time slices in a day
     *
     * @param args - MASS initialization arguments, # of processes, # of threads
     * @return unused
     */
    public Object init(Object args) {
        try {
            // Each place keeps track of its time position
            myIndex = index[0];

        } catch (Exception ex) {
            pa.log(Level.SEVERE, "exception caught in moisture flux", ex);
        }
        return (Object) null;
    }

    /**
     * @name computeMF - Open netCDF file and compute moisture flux over the
     *                   entire region
     * @param args unused
     * @return true = success, false = exception occurred
     */
    public Object computeMF(Object args) {
        
        double xW, yW, q;

        try {           
            // Retreive input values from input adapter
            xSpd = InputAdapter.read2DFloat(fileNames[myIndex], XWIND, XRANGE, YRANGE);
            ySpd = InputAdapter.read2DFloat(fileNames[myIndex], YWIND, XRANGE, YRANGE);
            moisture = InputAdapter.read2DFloat(fileNames[myIndex], MOISTURE, XRANGE, YRANGE);
        } catch (Exception e) {
            pa.log(Level.SEVERE, "exception caught in moisture flux", e);
        }
        
        try {
            // Compute moisture flux for all cells
            for (int x = 0; x < XRANGE; x++) {
                for (int y = 0; y < YRANGE; y++) {

                    // Moisture flux is computed using X,Y wind and moisture
                    xW = xSpd[x][y];
                    yW = ySpd[x][y];
                    q = moisture[x][y];
                    
                    qu[x][y] = q * xW;
                    qv[x][y] = q * yW;

                    mf[x][y] = q * Math.sqrt(Math.pow(xW, 2.0) + Math.pow(yW, 2.0));
                    if (yW != 0) {
                        dir[x][y] = Math.atan2(xW, yW);
                    } else if (xW > 0) {
                        dir[x][y] = 0.0;
                    } else if (xW < 0) {
                        dir[x][y] = Math.PI;
                    } else {
                        // No wind, so no moisture flux
                        dir[x][y] = 0.0;
                        mf[x][y] = 0.0;
                    }
                }
            }
            return (Object) true;
        } catch (Exception e) {
            pa.log(Level.SEVERE, "exception caught in moisture flux", e);
            return (Object) false;
        } finally {
            return (Object) null;
        }
    }

    /**
     * @name storeMFResults Record results to the results adapter
     * @param args - not used
     * @return unused
     */
    public Object storeMFResults(Object args) {
        try {
            // Iniitalize reference values
            Integer[] dimensions = new Integer[2];
            dimensions[0] = XRANGE;
            dimensions[1] = YRANGE;
            
            entryMagnitude = new SimpleEntry<>("magnitude", dimensions);
            entryDirection = new SimpleEntry<>("direction", dimensions);
            entryQU = new SimpleEntry<>("QU", dimensions);
            entryQV = new SimpleEntry<>("QV", dimensions);
            
            // Name the results files
            resultsName = "MoistureFlux-" + InputAdapter.readTime(fileNames[myIndex]);
            
            ra = new ResultsAdapter(ResultsAdapter.DOUBLE, resultsName, fileNames[myIndex], entryMagnitude, entryDirection, entryQU, entryQV);
            
            ra.log2DDouble("magnitude", mf);
            ra.log2DDouble("direction", dir);
            ra.log2DDouble("QU", qu);
            ra.log2DDouble("QV", qv);
            
            // free resources for garbage collection
            mf = null;
            dir = null;
            qu = null;
            qv = null;
            ra = null;

        } catch (Exception ex) {
            pa.log(Level.SEVERE, "exception caught in storeMFResults", ex);
        }
        return null;
    }
    
        
    public static int getMaxIterations() {
        return MAXITERATIONS;
    }
    
    /**
     * @name setFiles - Set file name array to store file names
     * @param fileArray - Array containing output result file names
     */
    public static void setFiles(String[] fileArray) {
        //find bugs complaining that fileArray is mutable, but filenames is a
        // static variable. suggests making a copy of fileArray
        fileNames = fileArray;
        fileRange = fileNames.length;
        
    }

    /**
     * @name initialize - Initialize MASS, Places objects, and other data
     *                    structured
     * @param status - Status adapter associated with this analytics module
     */
    public static void initialize(StatusAdapter status, int handle) {
        try {

            // Dummy object required for some MASS calls
            Object[] fixer = new Object[fileRange];
//                Object[] fixer = new Object[segLength];

            // Provenance collection
            status.reportMessage("Storing Metadata");
            SimpleEntry<String, String>[] provenanceEntries = collectProvenance();
            pa = new ProvenanceAdapter("MoistureFluxProvenance", fileNames[0]); //all files have same global attributes, only need to pull from one
            pa.create(provenanceEntries);
            
            // Start MASS
            status.reportMessage("Starting MASS");
            MASS.init(MASSARGS, NPROCESSES, NTHREADS);
            
                        // Initialize parallel computing space (Places object)
//            pacNorthwest = new Places(HANDLE, "MoistureFlux", null,
//                    fileRange);
            pacNorthwest = new Places(handle, NAME, null, fileRange);
            status.reportMessage("Computational Nodes created");
            
            // Initialize all computing nodes
            pacNorthwest.callAll(init_, fixer);
            status.reportMessage("Computational Nodes initialized");
            

        } catch (Exception ex) {
            pa.log(Level.SEVERE, "exception caught in moisture flux", ex);
        }
    }

    /**
     * @name runAnalysis Performs entire analysis, manages MASS
     *
     * @param stat StatusModel UI view
     * @param initialized set to true if MASS has already been initialized
     * @throws Exception
     */
    public static long runAnalysis(StatusAdapter sm) throws Exception {
        // VARIABLES
        long start, end; // data provenance for execution time
        long totalTime = -1;

        // dummy object to prevent array over- or underflow
        Object[] fixer = new Object[fileRange];
        
        // regional temperature calculations
        try {
            
            

            
            
            sm.reportMessage("Starting MASS");
            // begin time measurement
            start = new Date().getTime();

            // Compute moisture flux
            pacNorthwest.callAll(computeMF_, fixer);
            sm.reportMessage("Calculations completed");

            pacNorthwest.callAll(storeMFResults_, fixer);

            end = new Date().getTime();
            totalTime = end - start;    //milliseconds

            sm.reportMessage("Results stored");

            System.err.println("Segment execution time (milliseconds): "
                    + totalTime);
            sm.reportMessage("Segment execution time (milliseconds): "
                    + totalTime);
            
            pacNorthwest = null;
        } catch (Exception e) {
            pa.log(Level.SEVERE, "exception caught in moisture flux", e);
        }
        finish();
        return totalTime;
    }

    /**
     * @name collectProvenance - Returns a SimpleEntry array holding provenance
     *                           data
     * @return SimpleEntry - Holds provenance key-value pairs
     */
    private static SimpleEntry<String, String>[] collectProvenance() {
        SimpleEntry<String, String>[] provenanceEntries = new SimpleEntry[METADATALENGTH];
        provenanceEntries[0] = new SimpleEntry<>("Controlling_Node", MASSARGS[0]);
        provenanceEntries[1] = new SimpleEntry<>("Controlling_Node_Password", MASSARGS[1]);
        provenanceEntries[2] = new SimpleEntry<>("Machine_Life_File_Name", MASSARGS[2]);
        provenanceEntries[3] = new SimpleEntry<>("Port_Number", MASSARGS[3]);
        provenanceEntries[4] = new SimpleEntry<>("No_of_Processes", String.valueOf(NPROCESSES));
        provenanceEntries[5] = new SimpleEntry<>("No_of_Threads", String.valueOf(NTHREADS));
        provenanceEntries[6] = new SimpleEntry<>("No_of_Nodes", String.valueOf(fileRange));

        return provenanceEntries;
    }

    /**
     * @name finish - Terminate and clean-up MASS by calling MASS's finish
     *                function
     */
    public static void finish() {
        MASS.finish();
    }
}
